package com.weiquan.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.weiquan.annotation.ApiLog;
import com.weiquan.entity.ApiLogRecord;
import com.weiquan.service.ApiLogService;
import com.weiquan.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;

/**
 * API日志切面
 * 用于记录接口请求日志
 */
@Aspect
@Component
@Slf4j
public class ApiLogAspect {

    @Autowired
    private ApiLogService apiLogService;

    @Autowired
    private ObjectMapper objectMapper;

    private static final int MAX_REQUEST_SIZE = 10240; // 10KB
    private static final int MAX_RESPONSE_SIZE = 10240; // 10KB

    /**
     * 定义切点：拦截带有@ApiLog注解的方法
     */
    @Pointcut("@annotation(com.weiquan.annotation.ApiLog)")
    public void apiLogPointcut() {}

    /**
     * 环绕通知：记录接口调用日志
     */
    @Around("apiLogPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return joinPoint.proceed();
        }
        
        HttpServletRequest request = attributes.getRequest();
        
        // 创建日志记录对象
        ApiLogRecord logRecord = new ApiLogRecord();
        logRecord.setCreateTime(LocalDateTime.now());
        
        try {
            // 填充基本请求信息
            fillBasicInfo(logRecord, joinPoint, request);
            
            // 执行目标方法
            Object result = joinPoint.proceed();
            
            // 记录成功日志
            recordSuccessLog(logRecord, result, startTime);
            
            return result;
            
        } catch (Exception e) {
            // 记录异常日志
            recordExceptionLog(logRecord, e, startTime);
            throw e;
        }
    }

    /**
     * 填充基本请求信息
     */
    private void fillBasicInfo(ApiLogRecord logRecord, JoinPoint joinPoint, HttpServletRequest request) {
        try {
            // 获取方法签名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            
            // 获取@ApiLog注解信息
            ApiLog apiLog = method.getAnnotation(ApiLog.class);
            if (apiLog != null) {
                logRecord.setOperation(apiLog.value());
                logRecord.setOperationType(apiLog.operationType());
            } else {
                // 如果没有注解，使用方法名作为操作描述
                logRecord.setOperation(method.getName());
                logRecord.setOperationType("API_CALL");
            }
            
            // 请求基本信息
            logRecord.setMethod(request.getMethod());
            logRecord.setUrl(request.getRequestURL().toString());
            logRecord.setUri(request.getRequestURI());
            logRecord.setRequestIp(IpUtils.getClientIp(request));
            logRecord.setUserAgent(request.getHeader("User-Agent"));
            
            // 获取用户信息
            Object userIdAttr = request.getAttribute("userId");
            if (userIdAttr != null) {
                logRecord.setUserId(Long.valueOf(userIdAttr.toString()));
            }
            
            // 记录请求参数
            if (apiLog == null || apiLog.logParams()) {
                String requestParams = getRequestParams(request, joinPoint.getArgs());
                logRecord.setRequestParams(requestParams);
            }
            
        } catch (Exception e) {
            log.warn("填充基本请求信息失败: {}", e.getMessage());
        }
    }

    /**
     * 记录成功日志
     */
    private void recordSuccessLog(ApiLogRecord logRecord, Object result, long startTime) {
        try {
            long endTime = System.currentTimeMillis();
            logRecord.setExecutionTime(endTime - startTime);
            logRecord.setStatus(0); // 0-成功
            
            // 记录响应结果
            String responseData = getResponseData(result);
            logRecord.setResponseResult(responseData);
            
            // 异步保存日志
            apiLogService.saveApiLog(logRecord);
            
            // 打印控制台日志
            log.info("API调用成功 - 接口: {} {}, 用户: {}, 耗时: {}ms", 
                    logRecord.getMethod(), 
                    logRecord.getUrl(),
                    logRecord.getUserId() != null ? logRecord.getUserId() : "匿名用户",
                    logRecord.getExecutionTime());
                    
        } catch (Exception e) {
            log.error("记录成功日志失败", e);
        }
    }

    /**
     * 记录异常日志
     */
    private void recordExceptionLog(ApiLogRecord logRecord, Exception e, long startTime) {
        try {
            long endTime = System.currentTimeMillis();
            logRecord.setExecutionTime(endTime - startTime);
            logRecord.setStatus(1); // 1-失败
            
            // 记录异常信息
            String exceptionMsg = e.getClass().getSimpleName() + ": " + e.getMessage();
            if (exceptionMsg.length() > 2000) {
                exceptionMsg = exceptionMsg.substring(0, 2000) + "...";
            }
            logRecord.setExceptionMsg(exceptionMsg);
            
            // 异步保存日志
            apiLogService.saveApiLog(logRecord);
            
            // 打印控制台日志
            log.error("API调用异常 - 接口: {} {}, 用户: {}, 耗时: {}ms, 异常: {}", 
                    logRecord.getMethod(), 
                    logRecord.getUrl(),
                    logRecord.getUserId() != null ? logRecord.getUserId() : "匿名用户",
                    logRecord.getExecutionTime(),
                    e.getMessage());
                    
        } catch (Exception ex) {
            log.error("记录异常日志失败", ex);
        }
    }

    /**
     * 获取请求参数
     */
    private String getRequestParams(HttpServletRequest request, Object[] args) {
        try {
            Map<String, Object> params = new HashMap<>();
            
            // 获取URL参数
            Map<String, String[]> parameterMap = request.getParameterMap();
            if (parameterMap != null && !parameterMap.isEmpty()) {
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    String[] values = entry.getValue();
                    if (values.length == 1) {
                        params.put(entry.getKey(), values[0]);
                    } else {
                        params.put(entry.getKey(), Arrays.asList(values));
                    }
                }
            }
            
            // 获取请求体参数（POST/PUT等）
            if (args != null && args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    if (arg != null && isSerializableArg(arg)) {
                        try {
                            // 对于简单类型，直接添加
                            if (isSimpleType(arg)) {
                                params.put("arg" + i, arg);
                            } else {
                                // 对于复杂对象，尝试序列化
                                params.put("arg" + i, arg);
                            }
                        } catch (Exception e) {
                            // 如果序列化失败，记录参数类型
                            params.put("arg" + i, "[" + arg.getClass().getSimpleName() + "]");
                        }
                    }
                }
            }
            
            String json = objectMapper.writeValueAsString(params);
            
            // 限制请求参数长度
            if (json.length() > MAX_REQUEST_SIZE) {
                return json.substring(0, MAX_REQUEST_SIZE) + "...[数据过长，已截断]";
            }
            
            return json;
        } catch (Exception e) {
            log.warn("获取请求参数失败: {}", e.getMessage());
            return "{}";
        }
    }
    
    /**
     * 获取响应数据
     */
    private String getResponseData(Object result) {
        try {
            if (result == null) {
                return null;
            }
            
            String json = objectMapper.writeValueAsString(result);
            
            // 限制响应数据长度，避免存储过大的数据
            if (json.length() > MAX_RESPONSE_SIZE) {
                return json.substring(0, MAX_RESPONSE_SIZE) + "...[数据过长，已截断]";
            }
            
            return json;
        } catch (Exception e) {
            log.warn("获取响应数据失败: {}", e.getMessage());
            return result.toString();
        }
    }
    
    /**
     * 判断参数是否可序列化
     */
    private boolean isSerializableArg(Object arg) {
        if (arg == null) {
            return false;
        }
        
        // 排除不可序列化的类型
        Class<?> clazz = arg.getClass();
        String className = clazz.getName();
        
        // 排除Servlet相关类
        if (className.contains("javax.servlet") || 
            className.contains("org.springframework.web") ||
            className.contains("org.springframework.security") ||
            className.contains("org.apache.catalina")) {
            return false;
        }
        
        // 排除特定类型
        if (arg instanceof HttpServletRequest || 
            arg instanceof HttpServletResponse || 
            arg instanceof MultipartFile ||
            arg instanceof InputStream ||
            arg instanceof OutputStream) {
            return false;
        }
        
        return true;
    }
    
    /**
     * 判断是否为简单类型
     */
    private boolean isSimpleType(Object obj) {
        if (obj == null) {
            return true;
        }
        
        Class<?> clazz = obj.getClass();
        return clazz.isPrimitive() || 
               clazz == String.class ||
               clazz == Boolean.class ||
               clazz == Character.class ||
               Number.class.isAssignableFrom(clazz) ||
               Date.class.isAssignableFrom(clazz);
    }
}